﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Security;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Runtime.CompilerServices;
using System.Windows.Data;

namespace OmniKits.Windows.Behaviors
{
    using Extensions;

    public static class BindableSecurePassword
    {
        public static readonly SecureString EmptySecureString = new SecureString().Do(ss => ss.MakeReadOnly());

        private static readonly DependencyProperty Updating = DependencyProperty.RegisterAttached("Updating", typeof(bool), typeof(BindableSecurePassword));
        private static bool GetUpdating(this PasswordBox target) => (bool)target.GetValue(Updating);
        private static void SetUpdating(this PasswordBox target, bool value) => target.SetValue(Updating, value);

        private static readonly DependencyProperty AutoSyncBehavior = DependencyProperty.RegisterAttached("AutoSyncBehavior", typeof(AutoSyncSecurePasswordBehavior), typeof(BindableSecurePassword),
            new PropertyMetadata(null, SecurePasswordSyncChanged));
        private static void SecurePasswordSyncChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var old = e.OldValue as AutoSyncSecurePasswordBehavior;
            if (old != null)
                old.Detach();
            var @new = e.NewValue as AutoSyncSecurePasswordBehavior;
            if (@new != null)
                @new.Attach(d);
        }

        public static readonly DependencyProperty AutoSyncProperty = DependencyProperty.RegisterAttached("AutoSync", typeof(bool), typeof(BindableSecurePassword),
            new PropertyMetadata(false, AutoSyncChanged));
        public static bool GetAutoSync(this PasswordBox target) => (bool)target.GetValue(AutoSyncProperty);
        public static void SetAutoSync(this PasswordBox target, bool value) => target.SetValue(AutoSyncProperty, value);
        private static void AutoSyncChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var b = d.GetValue(AutoSyncBehavior) as AutoSyncSecurePasswordBehavior;
            if ((bool)e.NewValue)
            {
                if (b == null)
                    d.SetValue(AutoSyncBehavior, new AutoSyncSecurePasswordBehavior());
            }
            else
            {
                if (b != null)
                    d.SetValue(AutoSyncBehavior, null);
            }
        }

        public static readonly DependencyProperty AlwaysUpdateSourceProperty = DependencyProperty.RegisterAttached("AlwaysUpdateSource", typeof(bool), typeof(BindableSecurePassword),
            new PropertyMetadata(false, AlwaysUpdateSourceChanged));
        public static bool GetAlwaysUpdateSource(this PasswordBox target) => (bool)target.GetValue(AlwaysUpdateSourceProperty);
        public static void SetAlwaysUpdateSource(this PasswordBox target, bool value) => target.SetValue(AlwaysUpdateSourceProperty, value);
        private static void AlwaysUpdateSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var pb = d as PasswordBox;
            if (pb == null) return;

            if ((bool)e.NewValue)
                pb.UpdateSource(true);
        }

        public static readonly DependencyProperty SecurePasswordProperty = DependencyProperty.RegisterAttached("SecurePassword", typeof(SecureString), typeof(BindableSecurePassword),
            new FrameworkPropertyMetadata
            {
                BindsTwoWayByDefault = true,
                CoerceValueCallback = CoerceSecurePassword,
                DefaultUpdateSourceTrigger = UpdateSourceTrigger.LostFocus,
                IsAnimationProhibited = true,
            });

        private static object CoerceSecurePassword(DependencyObject d, object baseValue)
        {
            var pb = d as PasswordBox;
            if (pb == null) return baseValue;

            if (pb.GetUpdating())
                return baseValue;

            var autoSync = pb.GetAutoSync();
            var alwaysUpdate = pb.GetAlwaysUpdateSource();

            if (!alwaysUpdate)
                return pb.GetSecurePassword();

            d.Dispatcher.BeginInvoke((Action)(() =>
            {
                if (autoSync)
                    pb.SyncSecurePassword(true);
                else
                    pb.UpdateSource(true);
            }));

            return DependencyProperty.UnsetValue;
        }

        private static void UpdateSecurePassword(this PasswordBox passwordBox, SecureString securePassword, bool forceUpdate = false)
        {
            var updating = passwordBox.GetUpdating();
            try
            {
                passwordBox.SetUpdating(true);

                securePassword?.MakeReadOnly();

                passwordBox.SetValue(SecurePasswordProperty, securePassword);

                if (forceUpdate)
                {
                    var binding = passwordBox.GetBindingExpression(SecurePasswordProperty);
                    binding?.UpdateSource();
                }
            }
            finally
            {
                passwordBox.SetUpdating(updating);
            }
        }

        private static void UpdateSource(this PasswordBox passwordBox, bool forceUpdate = false)
        {
            passwordBox.UpdateSecurePassword(passwordBox.GetSecurePassword(), forceUpdate);
        }

        public static void SyncSecurePassword(this PasswordBox passwordBox, bool forceUpdate = false)
        {
            passwordBox.UpdateSecurePassword(passwordBox.SecurePassword, forceUpdate);
        }

        public static SecureString GetSecurePassword(this PasswordBox target)
        {
            return (SecureString)target.GetValue(SecurePasswordProperty);
        }
        // don't remove SetSecurePassword, or the XAML compiler will argue
        public static void SetSecurePassword(this PasswordBox target, SecureString securePassword)
        {
            target.SetValue(SecurePasswordProperty, securePassword);
        }

    }
}
